package net.sourceforge.openclixml;

import net.sourceforge.openclixml.autogen.RuleType;
import net.sourceforge.openclixml.environment.Environment;
import net.sourceforge.openclixml.ext.RulesType;

import org.w3c.dom.Document;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;


/**
 * This is the main class of clixml. It contains the main() method to launch clixml and all the helper methods to build up the clixml schemas and the documents under test.
 * @version $Revision: 47 $
 * @author dominikjungo
 *
 * Last changed: $Date: 2008-02-03 22:01:31 +0100 (Sun, 03 Feb 2008) $
 * The License for this file can be found in doc/Licenses/license-OpenCliXML.txt
 */
public class Validator implements IValidator {
    /**
     * Builds a Document with data read from an InputStream. This helper method can be used to build up the SUT Document.
     * @param is the InputStream containing the data to build up the Document.
     * @return the Document build up by reading from the InputStream
     * @throws net.sourceforge.openclixml.ClixmlException if the Document could not be build up. This can be a problem with the InputStream or Errorneous XML data.
     */
    public static Document getDocument(final InputStream is)
        throws ClixmlException {
        Document doc = null;

        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);

            //dbf.setValidating(true);
            DocumentBuilder db = dbf.newDocumentBuilder();
            doc = db.parse(is);

            return doc;
        } catch (Exception ex) {
            throw new ClixmlException("Document under test could not be built up",
                ex);
        }
    }

    /**
     * Gets the clixml rules by reading data from an inputstream.
     * @param is The inputstream from which the data is read to build up the clixml rules.
     * @return the clixml rules contained in the inputstream
     * @throws net.sourceforge.openclixml.ClixmlException if the rules document cannot be build up. This can either be invaild clixml or a problem with the input stream.
     */
    public static JAXBElement<net.sourceforge.openclixml.autogen.RulesType> getRules(
        final InputStream is) throws ClixmlException {
        try {
            JAXBContext jc = JAXBContext.newInstance(
                    "net.sourceforge.openclixml.autogen");
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            @SuppressWarnings(value = "unchecked")
            JAXBElement<net.sourceforge.openclixml.autogen.RulesType> o = (JAXBElement<net.sourceforge.openclixml.autogen.RulesType>) unmarshaller.unmarshal(is);

            return o;
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new ClixmlException("Rules doucment could not be built up", ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    public RulesType validate(final Document systemUnderTest)
        throws ClixmlException {
        try {
            org.w3c.dom.NodeList nl = systemUnderTest.getElementsByTagName(
                    "schema");
            org.w3c.dom.Attr location = (org.w3c.dom.Attr) nl.item(0)
                                                             .getAttributes()
                                                             .getNamedItem("location");
            java.lang.String clixmlRuleLocation = location.getValue();

            javax.xml.bind.JAXBContext jc = javax.xml.bind.JAXBContext.newInstance(
                    "net.sourceforge.openclixml.autogen");
            javax.xml.bind.Unmarshaller unmarshaller = jc.createUnmarshaller();
            @SuppressWarnings(value = "unchecked")
            javax.xml.bind.JAXBElement<net.sourceforge.openclixml.autogen.RulesType> o =
                (javax.xml.bind.JAXBElement<net.sourceforge.openclixml.autogen.RulesType>) unmarshaller.unmarshal(new java.io.File(
                        clixmlRuleLocation));
            net.sourceforge.openclixml.autogen.RulesType r = o.getValue();
            Map<String, Document> m = new HashMap();
            m.put("", systemUnderTest);

            return validate(m, r);
        } catch (JAXBException ex) {
            throw new ClixmlException("Rules doucment could not be built up", ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    public RulesType validate(final Map<String, Document> systemUnderTest,
        final net.sourceforge.openclixml.autogen.RulesType rules)
        throws ClixmlException {
        RulesType result = new net.sourceforge.openclixml.ext.RulesType();
        Iterator<RuleType> iter = rules.getRule().iterator();

        while (iter.hasNext()) {
            result.getRule().add(iter.next());
        }

        result.eval(new Environment(), systemUnderTest);

        return result;
    }

    /**
     * Transforms a StringArray into a Map. The key of the map is a namespace and the value is a document.
     * @param args Strings of format <tt>namespace=documenturl</tt> and one entry of format <tt>documenturl</tt>
     * @return a map with namespaces as keys and documents as values. The default namespace is represented be the empty string.
     * @throws net.sourceforge.openclixml.ClixmlException if no Default Namespace is defined or if a document at a given url cannot be found.
     */
    public static Map<String, Document> argsToMap(final String[] args)
        throws ClixmlException {
        Map<String, Document> result = new HashMap();

        for (String arg : args) {
            String[] nameAndLocation = arg.split("=");

            try {
                if (nameAndLocation.length == 1) {
                    result.put("",
                        getDocument(new FileInputStream(nameAndLocation[0])));
                } else {
                    result.put(nameAndLocation[0],
                        getDocument(new FileInputStream(nameAndLocation[1])));
                }
            } catch (FileNotFoundException e) {
                throw new ClixmlException("Declared file not found: ", e);
            }
        }

        if (result.get("") == null) {
            throw new ClixmlException(
                "No default document declared. A default document is needed.");
        }

        return result;
    }

    /**
     * Validates the document and outputs the result to {@link System.out}.
     * @param args list of names and document urls. one entry must be without name. the entry without name is the default namespace. the others must be of the format <tt>namespace=documenturl</tt>
     * @throws java.lang.Exception if the validation fails.
     */
    public void validate(final String[] args) throws Exception {
        //build the rules document
        JAXBElement<net.sourceforge.openclixml.autogen.RulesType> o = getRules(new FileInputStream(
                    args[0]));
        net.sourceforge.openclixml.autogen.RulesType r = o.getValue();

        //read the arguments from command line, put them into a map and start the validator.
        new Validator().validate(argsToMap(args), r);

        JAXBContext jc = JAXBContext.newInstance(
                "net.sourceforge.openclixml.autogen");
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
            Boolean.TRUE);
        marshaller.marshal(o, System.out);
    }

    /**
     * The entry point, launcher, for the CLiXML Validator.
     * @param args list of names and document urls. one entry must be without name. the entry without name is the default namespace. the others must be of the format <tt>namespace=documenturl</tt>
     * @throws Exception if the validation fails.
     */
    public static void main(final String[] args) throws Exception {
        try {
            new Validator().validate(args);
        } catch (ClixmlException e) {
            System.err.println(e.getMessage());
            System.exit(ReturnTypes.GOTEXCEPTION.getCode());
        }

        /*if(.isSuccess()){
        System.exit(0);
        }else{
        System.exit(-1);
        }*/
    }
}
